'use strict'
const db = require('./db')
const errcode = require('./errcode')
const constants = require('./constants')
const idate = require('./idate')
const csv = require('csv')
const xlsx = require('node-xlsx')

exports.errcode = errcode
exports.db = db
exports.constants = constants
exports.idate = idate

exports.success = (data) => {
  return { ...errcode.SUCCESS, result: data }
}

/**
 * @param {string|{ret, msg}|Error} msg
 * @param {any} data
 * **/
exports.error = (msg, data) => {
  if (typeof msg === 'object') {
    if (msg instanceof Error) {
      return { ...errcode.UNKNOW_ERROR, msg: msg.message, result: data }
    }

    return { ...errcode.UNKNOW_ERROR, ...msg, result: data }
  } else if (typeof msg === 'string') {
    return { ...errcode.UNKNOW_ERROR, msg: msg }
  }

  return { ...errcode.UNKNOW_ERROR, result: data }
}

exports.isEmpty = obj => {
  for (const k in obj) {
    return false
  }
  return true
}

exports.isArr = c => {
  return Object.prototype.toString.call(c) === '[object Array]'
}

exports.isNull = v => {
  return Object.prototype.toString.call(v) === '[object Null]'
}

exports.isUndefined = c => {
  return typeof c === 'undefined'
}

exports.page = ctx => {
  let limit = +ctx.query.pageSize || 10
  limit = limit > 100 ? 100 : limit
  const page = +ctx.query.page || 1
  const offset = (page - 1) * limit

  return { limit, offset }
}

exports.list2dict = (arr, key) => {
  const dict = {}
  for (const i in arr) {
    dict[arr[i][key]] = arr[i]
  }

  return dict
}

exports.asyncFunc = (fn) => {
  return async function (req, res, next) {
    try {
      await fn(req, res, next)
    } catch (err) {
      if (process.env.NODE_ENV !== 'prd') {
        console.error(err)
        res.json({ ...errcode.UNKNOW_ERROR, detail: err.message })
      } else {
        res.json({ ...errcode.UNKNOW_ERROR })
      }
    }
  }
}

exports.sleep = (t) => {
  // eslint-disable-next-line promise/param-names
  return new Promise(function (reoslve) {
    setTimeout(() => reoslve(), t)
  })
}

exports.filterOne = (arr, hanlde) => {
  for (const i in arr) {
    if (hanlde(arr[i], i, arr)) {
      return arr[i]
    }
  }

  return undefined
}

/**
 * 分片
 * @template T
 * @param {T[]} arr
 * @param {number} size - 分片大小， >= 1
 * @param {function} [fn] - 分片回调函数
 * @returns {T[][]} - 返回分片数组
 * **/
exports.fragment = function fragment (arr, size, fn) {
  if (!size || size < 0) throw new Error('`size` must be greater than or equal to 1')

  const length = arr.length
  const times = Math.ceil(length / size)
  const newArr = []
  const isFun = typeof fn === 'function'

  for (let i = 0; i < times; i++) {
    const start = i * size
    let end = start + size
    end = end > length ? length : end

    const chunk = arr.slice(start, end)

    newArr.push(isFun ? fn(chunk, i, start, end, arr) : chunk)
  }

  return newArr
}

exports.getIp = (req) => {
  return String(req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress).replace(/:ffff/, '')
}

/**
 * 保证运行指定时间
 * **/
exports.keepRunTime = (time, fn) => {
  return new Promise((resolve, reject) => {
    const s = Date.now();

    (async () => {
      try {
        const result = await fn()

        const val = (Date.now() - s)

        if (val >= time) return resolve(result)

        setTimeout(() => {
          resolve(result)
        }, time - val)
      } catch (err) {
        reject(err)
      }
    })()
  })
}

/**
 * 获取重合的范围
 * @template T
 * @param {[T, T]} range1
 * @param {[T, T]} range2
 * @returns {[T, T] | [null, null]}
 * **/
function getOverlayRange (range1, range2) {
  const start = [range1[0], range2[0], range2[1]].sort((a, b) => {
    return a < b ? -1 : 1
  })[1]
  const end = [range1[1], range2[0], range2[1]].sort((a, b) => {
    return a < b ? -1 : 1
  })[1]

  if (start < end) return [start, end]

  return [null, null]
}

exports.getOverlayRange = getOverlayRange

/**
 * 从某个范围减去另一个范围
 * @template T
 * @param {[T, T]} range1
 * @param {[T, T]} range2
 * @returns {[[T, T]] | [[T, T], [T, T]] | []}
 * **/
exports.rangeDifference = (range1, range2) => {
  const [start, end] = getOverlayRange(range1, range2)
  const ranges = []

  if (exports.isNull(start)) {
    if (range1[0] < range1[1]) return [[range1[0], range1[1]]]
    else return []
  }
  if (start === range1[0] && end === range1[1]) return []

  if (start > range1[0]) {
    ranges.push([range1[0], start])

    if (range1[1] > range2[1]) {
      ranges.push([end, range1[1]])
    }

    return ranges
  }

  if (end < range1[1]) {
    return [[end, range1[1]]]
  }

  return ranges
}

function getLeafPath (dict, leafId, rootValue = null) {
  const arr = []

  function loop (leafId) {
    const leaf = dict[leafId]

    if (leaf) {
      arr.unshift(leaf)

      if (leaf.parentId !== rootValue) {
        loop(leaf.parentId)
      }
    }
  }

  loop(leafId)

  return arr
}

exports.getLeafPath = getLeafPath

/**
 * @param {Request} res
 * @param {[{key: string, label: string, format?: string | function}]}
 * @param {Array} data
 * @param {object} options
 * @param {string} options.type - 文件类型，xlsx,csv,raw
 * **/
function exportFile (res, cols, data, options = { type: 'xlsx' }) {
  const filename = options.filename
  let buffer = Buffer.from('')

  const arr = data.map(item => {
    const chunk = []
    for (const i in cols) {
      const value = item[cols[i].key]
      if (cols[i].format) {
        chunk.push(typeof cols[i].format === 'function' ? cols[i].format(value, item) : cols[i].format)
      } else {
        chunk.push(value)
      }
    }
    return chunk
  })

  // 表头
  const head = cols.map(e => e.label)

  arr.unshift(head)

  res.attachment(`${filename}.${options.type}`)

  if (options.type === 'xlsx') {
    buffer = xlsx.build([{
      name: options.sheetName || 'sheet1',
      data: arr
    }])
  } else if (options.type === 'csv') {
    res.write('\uFEFF')
    csv.stringify(arr)
      .pipe(csv.parse())
      .pipe(csv.stringify())
      .pipe(res)
    return
  } else {
    buffer = Buffer.from(JSON.stringify(arr))
  }
  res.send(buffer)
}

exports.exportFile = exportFile
